import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Random;
import java.util.StringTokenizer;


public class Spaceship extends ShipObject
{
	// Size of the spaceship's image and hitbox
	//  the ship is a triangle with height 2*size, base 2*size/3
	private final int size = 15;
	
	public final static int POINTS = 100;
	
	private BufferedImage image;
	private BufferedImage accelImage;
	private BufferedImage livesImage;
	private int accelCounter = 0;
	
	// players 1 and 2 are controlled by the keyboard; any other 
	// number here indicates a rogue spaceship
	private int player; 
	
	// milliseconds where no collisions happen
	private static final int INVULN_MS = 2000;
	private int invulnTimer;
	
	public Spaceship(int x, int y, int player)
	{
		super(x,y);
		this.player = player;
		
		if(player == 1 || player == 2)
			invulnTimer = INVULN_MS;
		else
			invulnTimer = 0;
		
		if(player == 1)
			direction = Math.PI/2;
		else if(player == 2)
			direction = -Math.PI/2;
		else
			direction = AsteroidsGame.rng.nextDouble() * 2*Math.PI;
		
		drawImage();
		drawLivesImage(); // will only happen once
	}
	
	public Spaceship(StringTokenizer strtok)
	{
		super();
		loadState(strtok);
		
		drawImage();
		drawLivesImage(); // will only happen once
	}
	
	// starting locations for each player
	private static final int P1_X = AsteroidsGame.GAME_WIDTH/4;
	private static final int P1_Y = AsteroidsGame.GAME_HEIGHT/4;
	private static final int P2_X = AsteroidsGame.GAME_WIDTH*3/4;
	private static final int P2_Y = AsteroidsGame.GAME_HEIGHT*3/4;
	public Spaceship(int player)
	{
		this(player == 1?  P1_X : (player == 2? P2_X : 0),
			 player == 1? P1_Y : (player == 2? P2_Y : 0),
			 player);
	}
	
	// Implements getImage() of SpaceObject
	public BufferedImage getImage()
	{
		// flash when invulnerable
		if(invulnTimer > 0 && (invulnTimer / FRAME_RATE) % 2 == 0)
			return null;
		if(accelerating == 1)
			return accelImage;
		return image; 
	}
	
	public BufferedImage getLivesImage()
	{
		return livesImage;
	}
	
	public int getPlayer() { return player; }
	
	// Implements getWrapHeight and getWrapWidth of MovingObject
	public int getWrapWidth() { return size*3/2; } // this size does not include the tail
	public int getWrapHeight() { return size*3/2; }
	
	// Implement collision functions
	public CollisionType collisionType() 
	{
		if(invulnTimer > 0)
			return CollisionType.NONE;
		return CollisionType.TRIANGLE; 
	}
	public double[] collisionData()
	{
		// the three corners of the triangle
		// unrotated points are: (size, 0)
		//                       (-size, -2/3 size)
		//						 (-size, 2/3 size)
		// and we are multiplying by the rotation matrix:
		//  [ cos(angle)  -sin(angle) ]
		//  [ sin(angle)   cos(angle) ]
		double[] points = 
		{
			x + size*Math.cos(direction), 
			y + size*Math.sin(direction),
			x + (-size*Math.cos(direction) - -size*2/3*Math.sin(direction)),
			y + (-size*Math.sin(direction) + -size*2/3*Math.cos(direction)),
			x + (-size*Math.cos(direction) - size*2/3*Math.sin(direction)),
			y + (-size*Math.sin(direction) + size*2/3*Math.cos(direction)) 
		};
		return points;
	}
	
	public boolean isRogue() { return player != 1 && player != 2; }
	
	private void drawImage()
	{
		// coordinates for the polygons that make up the ship
		final int[] xvals_ship = {size, -size, -size/2, -size};
		final int[] yvals_ship = {0, -size*2/3, 0, size*2/3};
		final int[] xvals_tail = {-size*2/3, -size*2, -size*2/3};
		final int[] yvals_tail = {-size/4, 0, size/4};
		
		Color color;
		if(player == 1) color = Color.RED;
		else if(player == 2) color = Color.BLUE;
		else color = Color.WHITE;
		
		
		// two images, one for just the ship and one for ship + tail
		// both big enough to fit the image when rotated
		image = new BufferedImage(3*size, 3*size, BufferedImage.TYPE_INT_ARGB);
		accelImage = new BufferedImage(5*size, 5*size, BufferedImage.TYPE_INT_ARGB);
		
		// draw the ship images
		Graphics2D g = image.createGraphics();
		g.setColor(color);
		g.translate(image.getWidth()/2, image.getHeight()/2); // center of image
		g.rotate(direction);
		g.drawPolygon(xvals_ship, yvals_ship, xvals_ship.length);
		g.dispose();
		
		g = accelImage.createGraphics();
		g.setColor(color);
		g.translate(accelImage.getWidth()/2, accelImage.getHeight()/2);
		g.rotate(direction);
		g.drawPolygon(xvals_ship, yvals_ship, xvals_ship.length);
		g.drawPolyline(xvals_tail, yvals_tail, xvals_tail.length);
		g.dispose();
	}
	
	// draw a 20x20 small version of the ship, to be used in the lives display
	private void drawLivesImage()
	{
		// draw the ship, facing upward in a 20x20 image
		final int[] xvals_ship = {10, 3, 10, 17};
		final int[] yvals_ship = {0, 19, 12, 19};
		
		Color color;
		if(player == 1) color = Color.RED;
		else if(player == 2) color = Color.BLUE;
		else color = Color.WHITE;
		
		livesImage = new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB);
		
		// draw the ship images
		Graphics2D g = livesImage.createGraphics();
		g.setColor(color);
		g.drawPolygon(xvals_ship, yvals_ship, xvals_ship.length);
		g.dispose();
	}
	
	public void keyPress(KeyEvent e)
	{
		if(player == 1)
		{
    		if(e.getKeyCode() == KeyEvent.VK_UP)
        		accelerating = 1; // accelerate in direction spaceship is pointing
    		else if(e.getKeyCode() == KeyEvent.VK_DOWN)
    			accelerating = -1; // brake, reducing speed (this is not necessarily the opposite of up)
    		else if(e.getKeyCode() == KeyEvent.VK_LEFT)
        		turning = -1; // turn counterclockwise
    		else if(e.getKeyCode() == KeyEvent.VK_RIGHT)
        		turning = 1; // turn clockwise
    		else if(e.getKeyCode() == KeyEvent.VK_SHIFT)
    			shooting = true;
		}
		else if(player == 2)
		{
			if(e.getKeyCode() == KeyEvent.VK_W)
        		accelerating = 1; // accelerate in direction spaceship is pointing
    		else if(e.getKeyCode() == KeyEvent.VK_S)
    			accelerating = -1; // brake, reducing speed (this is not necessarily the opposite of up)
    		else if(e.getKeyCode() == KeyEvent.VK_A)
        		turning = -1; // turn counterclockwise
    		else if(e.getKeyCode() == KeyEvent.VK_D)
        		turning = 1; // turn clockwise
    		else if(e.getKeyCode() == KeyEvent.VK_E)
				shooting = true;
		}
	}
	
	public void keyRelease(KeyEvent e)
	{
		if(player == 1)
		{
    		if(e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN)
    			accelerating = 0;
    		else if(e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_RIGHT)
    			turning = 0;
    		else if(e.getKeyCode() == KeyEvent.VK_SHIFT)
    			shooting = false;
		}
		else if(player == 2)
		{
			if(e.getKeyCode() == KeyEvent.VK_W || e.getKeyCode() == KeyEvent.VK_S)
    			accelerating = 0;
    		else if(e.getKeyCode() == KeyEvent.VK_A || e.getKeyCode() == KeyEvent.VK_D)
    			turning = 0;
    		else if(e.getKeyCode() == KeyEvent.VK_E)
    			shooting = false;
		}
	}
	
	// Overrides move() from ShipObject
	public void move()
	{
		
		if(accelerating == 1)
			accelCounter++;
		if(accelerating == 1 && accelCounter == 4){
			AsteroidsGame.soundManager.play(AsteroidsGame.whir);
			accelCounter = 0;
		}
		
		
		if(player != 1 && player != 2)
			spaceShipAI();
		super.move();
		if(turning != 0)
			drawImage(); // need to redraw a rotated image
		invulnTimer -= 1000/FRAME_RATE;
	}
	
	protected void loadState(StringTokenizer strtok) throws NumberFormatException
	{
		super.loadState(strtok);
		player = Integer.parseInt(strtok.nextToken());
		invulnTimer = Integer.parseInt(strtok.nextToken());
		aiShootTimer = Integer.parseInt(strtok.nextToken());
		aiAccelTimer = Integer.parseInt(strtok.nextToken());
	}
	protected String saveState()
	{
		return "Spaceship " + super.saveState() + String.format("%d %d %d %d\n",
				player, invulnTimer, aiShootTimer, aiAccelTimer);
	}

	// Implements hit() from SpaceObject
	// an SpaceShip can be hit by a Bullet or Asteroid or Spaceship or GravitationalObject or AlienShip
	public boolean hit(SpaceObject by)
	{
		if(by instanceof Bullet)
		{
			if(((Bullet)by).isShooter(this))
				return false; // can't shoot myself
			if(((Bullet)by).isShooter(AsteroidsGame.game.player1)){
				AsteroidsGame.game.score(1, POINTS);
			}
			if(((Bullet)by).isShooter(AsteroidsGame.game.player2)){
				AsteroidsGame.game.score(2, POINTS);
			}
			AsteroidsGame.soundManager.play(AsteroidsGame.ship_explosion);
		}
		else if(by instanceof Spaceship)
		{
			// spaceships are the only object that can collide with their own class,
			// so need to make a special check for self-collision
			if(by == this)
				return false;
			if(isRogue() && ((Spaceship)by).isRogue()) // rogue spaceships have no effect on each other
				return false;
			// no points for destroying a rogue or your friend by crashing into them
			AsteroidsGame.soundManager.play(AsteroidsGame.ship_explosion_crash);
		}
		else if(by instanceof AlienShip || by instanceof Asteroid || by instanceof GravitationalObject)
		{
			if(isRogue())
				return false;
			
			// play explosion
			AsteroidsGame.soundManager.play(AsteroidsGame.ship_explosion_crash);
		}
		
		if(!isRogue())
			AsteroidsGame.game.playerKilled(player);
		delete();
		return true;
	}
	
	private static final int AI_SHOOT_DELAY = 3000; // ms between AI shots
	private static final double AI_ACCEL_CHANCE = 0.02; // per-frame chance of AI deciding to accelerate
	private static final int AI_ACCEL_DELAY = 2000; // minimum time between AI accelerations
	private static final int AI_ACCEL_TIME = 200; // time for which AI will accelerate when it decides to
	private static final double AI_SHOOT_ANGLE = Math.PI/16; // minimum angle to shoot rather than continue to turn
	
	private int aiShootTimer = 0;
	private int aiAccelTimer = 0;
	// rogue spaceships need to make decisions under their own power
	private void spaceShipAI()
	{
		aiShootTimer -= 1000/FRAME_RATE;
		aiAccelTimer -= 1000/FRAME_RATE;
		Random rng = AsteroidsGame.rng;
		
		// pick the closest player spaceship to shoot at
		Spaceship target;
		if(AsteroidsGame.game.player2 != null &&
				squareDistance(AsteroidsGame.game.player2) < squareDistance(AsteroidsGame.game.player1))
		{
			target = AsteroidsGame.game.player2;
		}
		else
		{
			target = AsteroidsGame.game.player1;
		}
		
		// figure out how we need to turn to face the target
		double dirChange = direction(target) - direction;
		if(dirChange < -Math.PI)
			dirChange += 2*Math.PI;
		else if(dirChange > Math.PI)
			dirChange -= 2*Math.PI;
		if(dirChange < -AI_SHOOT_ANGLE)
		{
    		turning = -1;
    		shooting = false;
		}
		else if(dirChange > AI_SHOOT_ANGLE)
		{
			turning = 1;
			shooting = false;
		}
		else // close enough, take the shot
		{
			turning = 0;
			if(aiShootTimer <= 0)
			{
				shooting = true;
				aiShootTimer = AI_SHOOT_DELAY;
			}
			else
				shooting = false;
		}
		
		// always accelerate if we're stopped or nearly stopped, otherwise 
		// accelerate in short bursts, rarely
		if(getSpeed() <= MAX_SPEED/5)
		{
			accelerating = 1;
			aiAccelTimer = 0;
		}
		else if(accelerating == 0 && aiAccelTimer <= 0 && rng.nextDouble() <= AI_ACCEL_CHANCE)
		{
			accelerating = 1;
			aiAccelTimer = AI_ACCEL_TIME;
		}
		else if(aiAccelTimer <= 0) // just finished accelerating
		{
			accelerating = 0;
			aiAccelTimer = AI_ACCEL_DELAY;
		}
	}

	
}
